package fr.gestproj.facade;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.primefaces.model.SortMeta;

import fr.gestproj.model.dto.DTORetourPagination;

/** @author Anne */
public abstract class AbstractFacade<T> {

	@PersistenceContext
	protected EntityManager em;
	protected Class entityClass;

	public AbstractFacade() {
	}

	@PostConstruct
	private void initialisation() {
		entityClass = (Class) ((java.lang.reflect.ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	public T find(final Serializable id) {
		return (T) em.find(entityClass, id);
	}

	public List<T> findAll() {
		CriteriaQuery query = initQuery();
		return getListe(query);
	}

	public DTORetourPagination<T> findByPagination(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {
		DTORetourPagination retourPagination = new DTORetourPagination();
		List<Predicate> listeConditions = new ArrayList();
		CriteriaQuery query = initQuery();
		Root root = query.from(entityClass);
		// Filtre sur les données
		for (String champ : filters.keySet()) {
			Object valeur = filters.get(champ);
			switch (valeur.getClass().getSimpleName()) {
			case "String":
				listeConditions.add(builder().like(root.get(champ), "%" + valeur + "%"));
				break;
			case "Boolean":
				listeConditions.add(builder().equal(root.get(champ), valeur));
				break;
			case "Date":
				listeConditions.add(builder().greaterThanOrEqualTo(root.get(champ), (Date) valeur));
				break;
			default:
				break;
			}
		}
		// Tri sur les données
		if (multiSortMeta != null) {
			List<Order> listeOrder = new ArrayList();
			for (SortMeta sortMeta : multiSortMeta) {
				switch (sortMeta.getSortOrder()) {
				case ASCENDING:
					listeOrder.add(builder().asc(root.get(sortMeta.getSortField())));
					break;
				case DESCENDING:
					listeOrder.add(builder().desc(root.get(sortMeta.getSortField())));
					break;
				default:
					break;
				}
			}
			query.orderBy(listeOrder);
		}
		creerConditions(query, listeConditions);
		retourPagination.setTotal(count(query));
		if (retourPagination.getTotal() > 0) {
			query.select(root);
			retourPagination.setListe(getListe(query, first, pageSize));
		}
		return retourPagination;
	}

	public T create(final T entity) {
		try {
			em.persist(entity);
			return entity;
		} catch (ConstraintViolationException e) {
			generateValidationException(e);
			return null;
		}
	}

	public T update(final T entity) {
		try {
			em.merge(entity);
			return entity;
		} catch (ConstraintViolationException e) {
			generateValidationException(e);
			return null;
		}
	}

	public void delete(final T entity) {
		em.remove(entity);
	}

	public List<T> getListe(CriteriaQuery query) {
		return em.createQuery(query).getResultList();
	}

	public List<T> getListe(CriteriaQuery query, int first, int max) {
		return em.createQuery(query).setFirstResult(first).setMaxResults(max).getResultList();
	}

	public T getOne(CriteriaQuery query) {
		try {
			return (T) em.createQuery(query).getSingleResult();
		} catch (Exception e) {
			return null;
		}
	}

	public int count() {
		CriteriaQuery query = initQuery();
		return count(query);
	}

	public int count(CriteriaQuery query) {
		query.select(builder().count(query.from(entityClass)));
		return ((Long) em.createQuery(query).getSingleResult()).intValue();
	}

	protected CriteriaBuilder builder() {
		return em.getCriteriaBuilder();
	}

	protected CriteriaQuery initQuery() {
		return builder().createQuery(entityClass);
	}

	private void creerConditions(CriteriaQuery query, List<Predicate> listeCondition) {
		query.where(listeCondition.toArray(new Predicate[listeCondition.size()]));
	}

	private void generateValidationException(ConstraintViolationException exception) {
		for (ConstraintViolation constraint : exception.getConstraintViolations()) {
			System.out.println(String.format("%s => %s", constraint.getPropertyPath(), constraint.getMessage()));
		}
	}

}
